---@module eventPost
require "log"
require "tool"
require "serviceConfig"
require "apiCache"
require "uart485"
require "math"
require "timerEx"
require "nvmConfig"
--require "handleTxt"
module(..., package.seeall)

-- 算法参数
local watchPointNum = 4      -- 观察近4个点
local upCheckVal = 5         -- 两个观察点间相差10kg可判断为加料
local downCheckVal = 5       -- 两个观察点间相差10kg可判断为减料
local upDown2NorCheckVal = 4 -- 从up，down 状态转为normal的条件，必须变化极小
local pointInteralSec = 45   -- 采样间隔

-- 记录近600个历史点
local valCache = {}
local valTimeCache = {}   -- 对应上面每一个采样点的时间
local LAST_VAL_LEN = 300
local curState = "normal" -- 料量没变化为normal up为料量上升中 down 为料量下降中
local startUpPoint = -1
local startDownPoint = -1

function returnState()
    return curState
end

local function addLast(newval)
    local num = #valCache
    if num < LAST_VAL_LEN then
        valCache[num + 1] = newval
        valTimeCache[num + 1] = tool.miscTime() -- 时间戳秒
        -- log.info("valCache==",table.concat(valCache,"-"))
        -- log.info("valTimeCache==",table.concat(valTimeCache,"-"))
        return num + 1
    else
        -- 删除第一条
        table.remove(valCache, 1)
        valCache[LAST_VAL_LEN] = newval

        table.remove(valTimeCache, 1)
        valTimeCache[LAST_VAL_LEN] = tool.miscTime() -- 时间戳秒

        -- 记录开始位置也要变化
        if startUpPoint > 1 then
            startUpPoint = startUpPoint - 1
        end
        if startDownPoint > 1 then
            startDownPoint = startDownPoint - 1
        end

        -- log.info("valCache==",table.concat(valCache,"-"))
        return LAST_VAL_LEN
    end
end

-- 使用近x个点判断当前的状态
local function checkCurState(lnum, lastState)
    local res = ""
    if #valCache > 6 then
        for i = 0, 6 do
            res = res .. valCache[lnum - i] .. ','
        end
    end

    log.info("TABLE DATA", res)

    -- 判断4个点是否是连续上升的状态
    local isup = true
    for i = 0, watchPointNum - 1 do
        if valCache[lnum - i] - upCheckVal < valCache[lnum - i - 1] then
            -- 有任何两个点间不满足 (n+1) - (n) < 上升判断值就不满足上升态
            isup = false
            break
        end
    end
    if isup == true then
        return "up"
    end

    -- 判断3个点是否是连续上升的状态
    local s_up = true
    if isup == false then
        for i = 0, 1 do
            if valCache[lnum - i] - upCheckVal < valCache[lnum - i - 1] then
                -- 有任何两个点间之间差距大于10kg，即触发小规模下料
                s_up = false
                break
            end
        end
    end

    if s_up == true and lastState ~= "up" then
        return "s_up"
    end

    -- 不是上升判断是不是下降
    local isdown = true
    for i = 0, watchPointNum - 1 do
        if valCache[lnum - i - 1] - valCache[lnum - i] < downCheckVal then
            -- 有任何两个点间不满足 (n) - (n+1) < 下降判断值 就不满足下降态
            isdown = false
            -- log.info("POINT", valCache[lnum - i - 1], valCache[lnum - i])
            break
        end
    end

    if isdown == true then
        return "down"
    end

    local s_down = true
    if isdown == false then
        for i = 0, 1 do
            if valCache[lnum - i - 1] - valCache[lnum - i] < downCheckVal then
                -- 有任何两个点间之间差距大于10kg，即触发小规模下料
                s_down = false
                break
            end
        end
    end

    if s_down == true and lastState ~= "down" then
        return "s_down"
    end

    -- 如果上一个状态是小规模下料，则判断连续5个点稳定，才认为进入稳定状态
    if lastState == "s_down" or lastState == "s_up" then
        for i = 0, 5 - 1 do
            if math.abs(valCache[lnum - i - 1] - valCache[lnum - i]) >
                upDown2NorCheckVal then
                -- 有任何两个点间不满足 (n) - (n+1) < 下降判断值 就不满足下降态
                return lastState
            end
        end
        -- 满足变化极小的条件
        return "s_normal"
    end

    -- 如果上一个状态是 上升或者下降，必须满足连续近几个点的变化极低，才会进入 normal ，否则还是原来的状态
    if lastState == "up" or lastState == "down" then
        for i = 0, watchPointNum - 1 do
            if math.abs(valCache[lnum - i - 1] - valCache[lnum - i]) >
                upDown2NorCheckVal then
                -- 有任何两个点间不满足 (n) - (n+1) < 下降判断值 就不满足下降态
                return lastState
            end
        end
        -- 满足变化极小的条件
        return "normal"
    end

    return "normal"
end

-- 每天发送后的清理历史数据
local function clearDayHistroy()
    local sbody = nvm.get("DAY_REPORT")
    sbody.dayfeedin = 0
    sbody.dayfeedinnum = 0
    sbody.dayfeedout = 0
    sbody.dayfeedoutnum = 0
    sbody.feedintime_min = 0
    sbody.feedouttime_min = 0
    sbody.dayremain = 0
    sbody.resultday = "2000-01-01"
    nvm.set("DAY_REPORT", sbody)
end

-- 上下料事件结束时向苍穹发生通知
--[[
{
  "imei": "867435056932106",
  "eventtime":"2021-05-21 22:25:00",
 "eventtype":"FEEDOUT", --事件类型 FEEDIN 进料 FEEDOUT-喂料  EMPTY-空仓（小于50kg） LACK-低料量（小于1000kg）
  "afterweight":3000,
  "changeweight":1000,
  "usetimesec":1300,
  "datapoint": [
    {"time": 1622616802,"val": 3200},
    {"time": 1622616802,"val": 3200},
    {"time": 1622616802,"val": 3200},
    {"time": 1622616802,"val": 3200},
    {"time": 1622616802,"val": 3200},
    {"time": 1622616802,"val": 3200},
  ]
}
]] --

local function handleEventPost(eventtype, curNum, edge)
    -- local curWeight = serviceConfig.getData().sensor.weightval
    local curWeight = valCache[curNum]
    local startWeight = curWeight
    local usetimesec = 0
    local startNum = -1
    local startTimestamp
    if eventtype == "FEEDIN" then
        -- 上料
        if startUpPoint < 1 then
            startUpPoint = 1 -- 避免没有监听到一开始的上升沿
        end
        startWeight = valCache[startUpPoint]
        startTimestamp = valTimeCache[startUpPoint]
        -- usetimesec = (curNum-startUpPoint)*pointInteralSec
        usetimesec = valTimeCache[curNum] - startTimestamp
        startNum = startUpPoint

        -- 日统计
        -- ["dayfeedin"] = 0, -- 当日补料量 kg
        -- ["dayfeedinnum"] = 0,-- 当日补料次数
        -- ["feedintime_min"] = 0,-- 当日补料总时间 单位分
        local sbody = nvm.get("DAY_REPORT")
        log.info("FEEDIN event", "startWeight=", startWeight, ",startUpPoint=",
            startUpPoint, ",curNum=", curNum, ",curWeight=", curWeight)
        log.info("DAY_REPORT==")
        tool.print_dump(sbody)
        -- 强校验，判断下料还是上料
        if curWeight - startWeight > 0 then
            sbody.dayfeedin = sbody.dayfeedin + curWeight - startWeight
            sbody.dayfeedinnum = sbody.dayfeedinnum + 1
            local usetimeMin, _ = math.modf(usetimesec / 60)
            sbody.feedintime_min = sbody.feedintime_min + usetimeMin
        elseif curWeight - startWeight < 0 then
            eventtype = "FEEDOUT"
            sbody.dayfeedout = sbody.dayfeedout + curWeight - startWeight
            sbody.dayfeedoutnum = sbody.dayfeedoutnum + 1
            local usetimeMin, _ = math.modf(usetimesec / 60)
            sbody.feedouttime_min = sbody.feedouttime_min + usetimeMin
        end
        if math.abs(curWeight - startWeight) >= 50 then
            sbody.resultday = tool.getTimeStrDay()
            nvm.set("DAY_REPORT", sbody)
        end
    elseif eventtype == "FEEDOUT" then
        -- 下料
        if startDownPoint < 1 then
            startDownPoint = 1 -- 避免没有监听到一开始的下降沿
        end
        startWeight = valCache[startDownPoint]
        -- usetimesec = (curNum-startDownPoint)*pointInteralSec
        usetimesec = valTimeCache[curNum] - valTimeCache[startDownPoint]
        --startTimestamp = valTimeCache[curNum]
        startTimestamp = valTimeCache[startDownPoint]
        startNum = startDownPoint

        -- 日统计
        -- ["dayfeedout"] = 0,-- 当日喂料总重量 单位kg
        -- ["dayfeedoutnum"] = 0,-- 当日喂料总次数
        -- ["feedouttime_min"] = 0,-- 当日喂料总时长 分
        local sbody = nvm.get("DAY_REPORT")
        log.info("FEEDOUT event", "startWeight=", startWeight,
            ",startDownPoint=", startDownPoint, ",curNum=", curNum,
            ",curWeight=", curWeight)
        log.info("DAY_REPORT==")
        tool.print_dump(sbody)
        -- 强校验，判断下料还是上料
        if curWeight - startWeight < 0 then
            sbody.dayfeedout = sbody.dayfeedout + curWeight - startWeight
            sbody.dayfeedoutnum = sbody.dayfeedoutnum + 1
            local usetimeMin, _ = math.modf(usetimesec / 60)
            sbody.feedouttime_min = sbody.feedouttime_min + usetimeMin
        elseif curWeight - startWeight > 0 then
            eventtype = "FEEDIN"
            sbody.dayfeedin = sbody.dayfeedin + curWeight - startWeight
            sbody.dayfeedinnum = sbody.dayfeedinnum + 1
            local usetimeMin, _ = math.modf(usetimesec / 60)
            sbody.feedintime_min = sbody.feedintime_min + usetimeMin
        end

        if math.abs(curWeight - startWeight) >= 10 then
            sbody.resultday = tool.getTimeStrDay()
            nvm.set("DAY_REPORT", sbody)
        end
    end

    local body = {
        imei = misc.getImei(),
        eventtime = tool.getTimeStr(),
        startTimestamp = startTimestamp,
        eventtype = eventtype,
        afterweight = curWeight,
        startweight = startWeight,
        changeweight = curWeight - startWeight,
        usetimesec = usetimesec,
        datapoint = {}
    }
    if edge ~= nil then
        body["upanddown"] = edge
    end
    -- 加入时间点
    -- local curTimestamp = tool.miscTime()
    -- log.info("curTimestamp=",curTimestamp)
    for i = startNum, curNum do
        log.info("valCache[i]", i, "--", valCache[i])
        -- local pointTimestamp = curTimestamp - (curNum - startNum + i) * pointInteralSec
        table.insert(body.datapoint,
            { ["val"] = valCache[i], ["time"] = valTimeCache[i] })
    end

    log.info("evenPost", json.encode(body))
    -- 变化重量大于10
    if (math.abs(curWeight - startWeight) >= 10 and eventtype == "FEEDOUT") or
        (math.abs(curWeight - startWeight) >= 50 and eventtype == "FEEDIN") then
        apiCache.request("POST", config.POSTEVENT_URL, json.encode(body), 6000, nil)
    end
end

-- 判断缓存的日期是否为今天
local function isNotToday(date)
    local today = tool.getTimeStrDay()
    if date == nil or date == "2000-01-01" then
        return false
    end

    if date ~= nil and today ~= nil then
        local today_list = tool.Split(today, "-")
        local date_list = tool.Split(date, "-")
        for i, v in ipairs(today_list) do
            if tonumber(today_list[i]) > tonumber(date_list[i]) then
                return true
            end
        end
    end

    return false
end

--[[
    -- 每天晚上固定一个时间上报今天料塔的情况
    ### 每日数据上报 每天晚上12点后尽快上报
    #  "imei": "867435056932106",
    #  "resultday":"2021-05-21", --上报的日期
    #  "dayfeedin":2000, -- 当日补料量 kg
    #  "dayfeedinnum":3,-- 当日补料次数
    #  "feedintime_min":120,-- 当日补料总时间 单位分
    #  "dayfeedout":3000,-- 当日喂料总重量 单位kg
    #  "dayfeedoutnum":1,-- 当日喂料总次数
    #  "feedouttime_min":20,-- 当日喂料总时长 分
    #  "dayremain":1000 --当日剩余料量 kg
]] --
local function handleDayPost()
    ---@type DAY_REPORT
    local sbody = nvm.get("DAY_REPORT")
    local body = tool.deepCopy(sbody)

    tool.print_dump(body)

    -- 重量异常时,获取valCache的最新重量记录
    local weight = serviceConfig.getData().sensor.weightval
    if weight < -111111 then
        local num = #valCache
        weight = valCache[num]
    end

    body["dayremain"] = weight
    body["imei"] = misc.getImei()
    body["resultday"] = tool.getTimeStrDay()

    log.info("handleDayPost", json.encode(body))
    -- handleTxt.write_txt(json.encode(body))
    apiCache.request("POST", config.DAYREPORT_URL, json.encode(body), 6000,
        function()
            -- 每天定时上报成功后清空今天的数据
            log.info("handleDayPost clear")
            clearDayHistroy()
        end)
end

--[[
]] --
function runTask1()
    sys.taskInit(function()
        sys.wait(4000)
        count = 0
        while true do
            if count < 8 then
                sys.wait(5 * 1000)
                count = count + 1
            else
                sys.wait(pointInteralSec * 1000) -- 1分钟一次采样
            end

            while true do
                local val = serviceConfig.getData().sensor.weightval
                if val < -111111 then
                    -- 异常值不分析不记录
                    -- 这里的左右主要是为了避免中途突然有一个异常值导致整个分析结果错误
                    break
                end

                local lnum = addLast(val)
                if lnum <= watchPointNum then
                    -- 采样点还不够
                    curState = "normal"
                    break
                end
                for i = 0, watchPointNum - 1 do
                    -- log.info("lnum-i",lnum,i,valCache[lnum-i])
                    if valCache[lnum - i] < -111111 then
                        -- 近x个测点有异常值，就跳过
                        curState = "normal"
                        break
                    end
                end

                local lastState = curState
                curState = checkCurState(lnum, lastState)
                log.info("runTask1,lastState=" .. lastState .. ",curState=" ..
                    curState)
                -- if lastState == "s_up" and curState == "s_down" then
                --     -- 异常
                --     log.info("eventPost--", "error", valCache[lnum - 5], valCache[lnum - 4], valCache[lnum - 3],valCache[lnum-3] - valCache[lnum - 5])
                if (lastState == "down" or lastState == "s_down") and
                    (curState == "up" or curState == "s_up") then
                    -- 上料的同时突然下料
                    -- 先推送上料的信息，注意这时的down其实已经是发现了延后了watchPointNum个值，斜率计算
                    if curState == "s_up" then
                        handleEventPost("FEEDOUT", lnum - 2, true)
                    else
                        handleEventPost("FEEDOUT", lnum - 4, true)
                    end

                    if curState == "s_up" then
                        startUpPoint = lnum - 2
                    else
                        startUpPoint = lnum - 4
                    end
                elseif (lastState == "up" or lastState == "s_up") and
                    (curState == "down" or curState == "s_down") then
                    -- 下料的同时突然上料
                    -- 先推送上料的信息，注意这时的down其实已经是发现了延后了watchPointNum个值，斜率计算
                    if curState == "s_down" then
                        handleEventPost("FEEDIN", lnum - 2, true)
                    else
                        handleEventPost("FEEDIN", lnum - 4, true)
                    end

                    if curState == "s_down" then
                        startDownPoint = lnum - 2
                    else
                        startDownPoint = lnum - 4
                    end
                elseif (lastState ~= "up" and lastState ~= "s_up") and
                    (curState == "up" or curState == "s_up") then
                    log.info("eventPost--", "start up,lnum==", lnum)
                    if curState == "s_up" then
                        startUpPoint = lnum - 2
                    else
                        startUpPoint = lnum - watchPointNum
                    end
                elseif lastState == "up" and curState == "normal" then
                    -- 完成了一次加料
                    log.info("eventPost--", "stop up,lnum==", lnum)
                    handleEventPost("FEEDIN", lnum, false)
                elseif lastState == "s_up" and curState == "s_normal" then
                    -- 完成了一次加料
                    log.info("eventPost--", "stop up,lnum==", lnum)
                    handleEventPost("FEEDIN", lnum, false)
                elseif (lastState ~= "down" and lastState ~= "s_down") and
                    (curState == "down" or curState == "s_down") then
                    -- 开始喂料
                    log.info("eventPost--", "start down,lnum==", lnum)
                    if curState == "s_down" then
                        startDownPoint = lnum - 2
                    else
                        startDownPoint = lnum - watchPointNum
                    end
                elseif lastState == "down" and curState == "normal" then
                    -- 完成喂料
                    log.info("eventPost--", "stop down,lnum==", lnum)
                    handleEventPost("FEEDOUT", lnum, false)
                elseif lastState == "s_down" and curState == "s_normal" then
                    -- 完成小批量喂料
                    log.info("eventPost--", "stop down,lnum==", lnum)
                    handleEventPost("FEEDOUT", lnum, false)
                elseif lastState == "up" and curState == "down" then
                    -- 上料的同时突然下料
                    -- 先推送上料的信息，注意这时的down其实已经是发现了延后了watchPointNum个值，斜率计算
                    handleEventPost("FEEDIN", lnum - watchPointNum, false)
                    startDownPoint = lnum - watchPointNum
                end

                break -- 一定要加避免死循环，实现类似于continue的效果
            end
        end
    end)
end

---- 测试每日上报
-- function testhandleDayPost()
-- sys.taskInit(function()
--        sys.wait(10000)
--        ---@type DAY_REPORT
--        local sbody = nvm.get("DAY_REPORT")
--        sbody.dayfeedin = sbody.dayfeedin + 1
--        sbody.dayfeedinnum = sbody.dayfeedinnum + 1
--        sbody.dayfeedout = sbody.dayfeedout + 1
--        sbody.dayremain = sbody.dayremain + 1
--        nvm.set("DAY_REPORT",sbody)
--        handleDayPost()
--    end)
--     readFeedData()
--    sys.taskInit(function()
--     -- 每日上报是在每天晚上11.30至12.00之间随即上报一次,这里因测试需要改为每分钟上报一次
--     sys.wait(1000 * 10)
--     while true do
--         sys.wait(1000 * 60)
--         log.info("开始每日上报")
--         handleDayPost()
--     end
--    end)
-- end

-- 模拟读取料塔控制器,每10秒读取一次。每次读取后会修改 serviceConfig.getData().sensor.weightval 的值
-- function readFeedData()
--     sys.taskInit(
--         function ()
--             while true do
--                 sys.wait(1000 * 10)
--                 -- -999999,-787555为异常数据，555555，666666为正常数据
--                 local weight = {-999999,555555,666666,-787555}
--                 -- 随机模拟weight表的数据
--                 local data = weight[math.floor(math.random(1,4))]
--                 serviceConfig.setWeight(data,"测试数据",nil)
--             end
--         end
--     )
-- end

--
---- 用于测试正常不用，发生api
-- function testhhandleEventPost()
--    sys.taskInit(function()
--        sys.wait(10000)
--        -- 构造测试假数据
--        startUpPoint = 1
--        startDownPoint = 1
--        for i=1,LAST_VAL_LEN do
--            table.insert(valCache,1000)
--        end
--        handleEventPost("FEEDIN",59)
--    end)
-- end
--
---- 测试算法
function testTask()
    -- 先停掉原来的485 更新重量值的任务
    uart485.TEST_MODE = true
    pointInteralSec = 3 -- 3秒一次采样
    -- 启动一个任务模拟更新重量值
    sys.taskInit(function()
        sys.wait(10000)
        local data_list = {
            13700, 13700, 13700, 900, 800, 700, 600, 500, 300, 5, 49, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 90, 130,
            150, 300, 340, 600, 13636, 13801, 13807, 13807, 13807, 13700, 13701,
            13700, 13700, 13700
        }
        while true do
            for i, v in ipairs(data_list) do
                serviceConfig.setWeight(v, "正常", "")
                sys.wait(pointInteralSec * 1000)
            end

            sys.wait(2000)
            -- handleDayPost()
        end
    end)
    -- runTask1()
end

-- 晚上23：30 ~ 23:59 间上报今天的情况
function runTask2()
    -- 通过ntp时间来判断当日上传
    sys.taskInit(function()
        sys.wait(63000)
        if nvm.get("RANDOM_NUM") == nil or nvm.get("RANDOM_NUM") == 0 then
            math.randomseed(os.time())
            local num = math.random(1, 30)
            nvm.set("RANDOM_NUM", num)
        end
        local num = nvm.get("RANDOM_NUM")
        while true do
            local t = misc.getClock()
            log.info("TIME REPORT", t["hour"], t["min"], num)
            if t["hour"] == 23 and t["min"] == 10 + num then
                handleDayPost()
                sys.wait(65000)
            end
            local sbody = nvm.get("DAY_REPORT")
            if isNotToday(sbody.resultday) then
                handleDayPost()
            end
            sys.wait(10000)
        end
    end)
end

-- 定时重启
function dailyRestart()
    -- 通过ntp时间来判断当日上传
    sys.taskInit(function()
        sys.wait(65000)
        math.randomseed(os.time())
        local num = nvm.get("RANDOM_NUM")
        while true do
            local t = misc.getClock()
            log.info("TIME REPORT", t["hour"], t["min"], num)
            if t["hour"] == 02 and t["min"] == 10 + num then
                sys.restart("DAILY_RESTART")
                -- sys.wait(65000)
            end
            sys.wait(10000)
        end
    end)
end
